package cn.wjssl.infrastructure.persistent.repository;

import cn.wjssl.domain.strategy.model.entity.StrategyAwardEntity;
import cn.wjssl.domain.strategy.model.entity.StrategyEntity;
import cn.wjssl.domain.strategy.model.entity.StrategyRuleEntity;
import cn.wjssl.domain.strategy.model.valobj.*;
import cn.wjssl.domain.strategy.repository.IStrategyRepository;
import cn.wjssl.domain.strategy.service.rule.chain.factory.DefaultChainFactory;
import cn.wjssl.infrastructure.persistent.dao.*;
import cn.wjssl.infrastructure.persistent.po.*;
import cn.wjssl.infrastructure.persistent.redis.IRedisService;
import cn.wjssl.types.common.Constants;
import cn.wjssl.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static cn.wjssl.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;

/**
 * 策略仓储实现类
 */
@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {

    // 通过 redis 查询信息
    @Resource
    private IRedisService redisService;
    // 通过 Mysql 查询信息
    @Resource
    private IStrategyAwardDao strategyAwardDao;
    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IStrategyRuleDao strategyRuleDao;
    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
    @Resource
    private IRaffleActivityDao activityDao;
    @Resource
    private IRaffleActivityAccountDao accountDao;

    /**
     * 根据 strategyId 查询策略奖品信息实体类 StrategyAwardEntity
     * @param strategyId
     * @return
     */
    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
        // 先通过redis查询, 没有再查 Mysql, 由于一个策略Id 对应一个list 所以直接用getValue()
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_LIST_KEY + strategyId;
        List<StrategyAwardEntity> strategyAwardEntities = redisService.getValue(cacheKey);
        if (strategyAwardEntities != null && !strategyAwardEntities.isEmpty()) {
            return strategyAwardEntities;
        }
        // 没有就从数据库里拿, 拿到的其实是 StrategyAwardPO, 还需要通过赋值给 strategyAwardEntities
        List<StrategyAwardPO> strategyAwardPOS = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        // 要重新初始化 strategyAwardEntities
        strategyAwardEntities = new ArrayList<>(strategyAwardPOS.size());
        for (StrategyAwardPO strategyAwardPO : strategyAwardPOS) {
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                    .strategyId(strategyAwardPO.getStrategyId())
                    .awardId(strategyAwardPO.getAwardId())
                    .awardTitle(strategyAwardPO.getAwardTitle())
                    .awardSubtitle(strategyAwardPO.getAwardSubtitle())
                    .awardCount(strategyAwardPO.getAwardCount())
                    .awardCountSurplus(strategyAwardPO.getAwardCountSurplus())
                    .awardRate(strategyAwardPO.getAwardRate())
                    .sort(strategyAwardPO.getSort())
                    .ruleModels(strategyAwardPO.getRuleModels())
                    .build();
            // 添加到 策略奖品的list中 strategyAwardEntities
            strategyAwardEntities.add(strategyAwardEntity);
        }
        // 还要把查询到的结果放到redis中,方便下次从redis中拿
        redisService.setValue(cacheKey, strategyAwardEntities);
        // 返回结果
        return strategyAwardEntities;
    }

    /**
     * 根据策略ID, 以及生成的对应的存放awardID的奖品池, 存放到redis
     * @param key
     * @param rateRange
     * @param shuffleStrategyAwardSearchRateTable
     */
    @Override
    public void storeStrategyAwardSearchRateTable(String key, int rateRange, Map<Integer, Integer> shuffleStrategyAwardSearchRateTable) {
        // 存储 奖品池的数量
        redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
        // 存储具体的奖品池,通过getMap,如果没有该key的话就创建map,如果有就修改
        redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key)
                .putAll(shuffleStrategyAwardSearchRateTable);
    }

    // 从redis中获取奖品池的大小
    @Override
    public int getRateRange(String key) {
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        if (!redisService.isExists(cacheKey)) {
            throw new AppException(UN_ASSEMBLED_STRATEGY_ARMORY.getCode(), cacheKey + Constants.COLON + UN_ASSEMBLED_STRATEGY_ARMORY.getInfo());
        }
        return redisService.getValue(cacheKey);
    }

    // 抽奖
    @Override
    public Integer getStrategyAwardAssemble(String key, int rateKey) {
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey);
    }

    /**
     * 根据 strategyId 查询策略信息实体类 StrategyEntity, 方便查询规则模型
     * 先从redis中查, 没有再从Mysql查, 查到后存到redis中
     * @param strategyId
     * @return
     */
    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        if (null != strategyEntity) return strategyEntity;
        StrategyPO strategyPO = strategyDao.queryStrategyByStrategyId(strategyId);
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategyPO.getStrategyId())
                .strategyDesc(strategyPO.getStrategyDesc())
                .ruleModels(strategyPO.getRuleModels())
                .build();
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;

    }

    /**
     * 根据 决策Id 和 ruleModel, 查询对应的 StrategyRuleEntity, 装配权重
     * 暂时不走 redis, 方便修改
     * @param strategyId
     * @param ruleModel
     * @return
     */
    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
        StrategyRulePO strategyRulePO= new StrategyRulePO();
        strategyRulePO.setStrategyId(strategyId);
        strategyRulePO.setRuleModel(ruleModel);
        StrategyRulePO strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRulePO);
        return StrategyRuleEntity.builder()
                .strategyId(strategyRuleRes.getStrategyId())
                .awardId(strategyRuleRes.getAwardId())
                .ruleType(strategyRuleRes.getRuleType())
                .ruleModel(strategyRuleRes.getRuleModel())
                .ruleValue(strategyRuleRes.getRuleValue())
                .ruleDesc(strategyRuleRes.getRuleDesc())
                .build();
    }

    /**
     * 根据 决策Id, 规则类型ruleModel, 奖品Id, 查询String类型的 rule_value
     * 查询的是 Strategy_rule表, 查询到的是 rule_value
     *
     * @param strategyId
     * @param ruleModel
     * @param awardId
     * @return rule_value
     */
    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel, Integer awardId) {
        StrategyRulePO strategyRulePO = new StrategyRulePO();
        strategyRulePO.setStrategyId(strategyId);
        strategyRulePO.setRuleModel(ruleModel);
        strategyRulePO.setAwardId(awardId);
        return strategyRuleDao.queryStrategyRuleValue(strategyRulePO);
    }

    /**
     * 根据 决策Id, awardId, 查询奖品对应的 rule_model
     * @param strategyId
     * @param awardId
     * @return 奖品对应的 rule_model 值对象
     */
    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId) {
        StrategyAwardPO strategyAwardPO = new StrategyAwardPO();
        strategyAwardPO.setStrategyId(strategyId);
        strategyAwardPO.setAwardId(awardId);
        String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAwardPO);
        return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();
    }

    /**
     * 根据 treeId ,构建决策树
     * @param treeId
     * @return
     */
    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {
        // 先从redis中查规则树VO, 没有再走数据库
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVO ruleTreeCacheVO = redisService.getValue(cacheKey);
        if (null != ruleTreeCacheVO) return ruleTreeCacheVO;

        // 没有要从数据库开始构建
        RuleTreePO ruleTreePO = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        List<RuleTreeNodePO> ruleTreeNodePOS = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        List<RuleTreeNodeLinePO> ruleTreeNodeLinePOS = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);

        // 构建节点连线VO对应的List的map , 用于构建节点的map, 其中 nodeFrom --> 节点的rule_key
        Map<String,List<RuleTreeNodeLineVO>> lineVOMap = new HashMap<>();
        for (RuleTreeNodeLinePO linePO : ruleTreeNodeLinePOS) {
            // 先对应PO到VO, 装填List, 再往Map里塞
            RuleTreeNodeLineVO lineVO = RuleTreeNodeLineVO.builder()
                    .treeId(treeId)
                    .ruleNodeFrom(linePO.getRuleNodeFrom())
                    .ruleNodeTo(linePO.getRuleNodeTo())
                    .ruleLimitType(RuleLimitTypeVO.valueOf(linePO.getRuleLimitType()))
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(linePO.getRuleLimitValue()))
                    .build();

            // 判断Map中是否已有ruleNodeFrom对应的列表
            List<RuleTreeNodeLineVO> tmpList = lineVOMap.get(linePO.getRuleNodeFrom());
            if (tmpList == null) {
                // 如果没有列表，创建一个新的
                tmpList = new ArrayList<>();
                lineVOMap.put(linePO.getRuleNodeFrom(), tmpList);
            }
            // 将lineVO添加到对应的列表中
            tmpList.add(lineVO);
        }

        // 有了连线list的对应关系, 就可以构建节点的map了
        Map<String, RuleTreeNodeVO> nodeVOMap = new HashMap<>();
        for (RuleTreeNodePO nodePO : ruleTreeNodePOS) {
            RuleTreeNodeVO nodeVO = RuleTreeNodeVO.builder()
                    .treeId(treeId)
                    .ruleKey(nodePO.getRuleKey())
                    .ruleDesc(nodePO.getRuleDesc())
                    .ruleValue(nodePO.getRuleValue())
                    .treeNodeLineVOList(lineVOMap.get(nodePO.getRuleKey()))
                    .build();
            nodeVOMap.put(nodePO.getRuleKey(), nodeVO);
        }

        // 有了节点的map 就可以构建规则树VO了
        RuleTreeVO treeVO = RuleTreeVO.builder()
                .treeId(treeId)
                .treeDesc(ruleTreePO.getTreeDesc())
                .treeName(ruleTreePO.getTreeName())
                .treeRootRuleNodeRuleKey(ruleTreePO.getTreeRootRuleKey())
                .treeNodeMap(nodeVOMap)
                .build();
        // 存入 redis, 并返回
        redisService.setValue(cacheKey, treeVO);
        return treeVO;
    }

    /**
     * 奖品库存初始化
     * @param cacheKey
     * @param awardCount
     */
    @Override
    public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
        if (redisService.isExists(cacheKey)) return;
        // cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        redisService.setAtomicLong(cacheKey, awardCount);
    }

    /**
     * 扣减库存
     * @param cacheKey
     * @return 是否扣减成功
     */
    @Override
    public Boolean subtractionAwardStock(String cacheKey, Date endDateTime) {
        // 计数器 -1 就是扣减后库存
        long surplus = redisService.decr(cacheKey);
        // 如果库存扣到小于0了, 那就是没有库存了就设置为0
        if (surplus < 0) {
            // 这里是最开始装配奖池时, 传入的初始库存, 设置为0
            // cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
            redisService.setValue(cacheKey, 0);
            return false;
        }
        // 1. 按照cacheKey decr 后的值，如 99、98、97 和 key 组成为库存锁的key进行使用。
        // 2. 加锁为了兜底，防止人为的误恢复库存。因为所有的可用库存key，都被加锁了。
        // lockKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + "_" + awardId + "_" + surplus;
        // 相当于扣减一个库存, 就往redis里存一个对应库存的key, value是setNx()方法中定义的value - "lock"
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Boolean lock = false;
        if (null != endDateTime) {  // 如果有
            long expireMillis = endDateTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
            lock = redisService.setNx(lockKey, expireMillis, TimeUnit.MILLISECONDS);
        } else {
            lock = redisService.setNx(lockKey);
        }
        if (!lock) {
            log.info("策略奖品库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    /**
     * 把库存扣减流转对象放入redis的延时队列, 让redis等一会儿再放到阻塞队列中, 让定时队列消费
     * @param strategyAwardStockKeyVO
     */
    @Override
    public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);
    }

    /**
     * 获取redis 阻塞队列头部第一个元素, 这是最先消耗的库存
     * @return
     */
    @Override
    public StrategyAwardStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        return blockingQueue.poll();

    }

    /**
     * 通过阻塞队列中获取的头部元素, 获得当前消耗的是哪个奖品的库存, 同步Mysql中的库存
     * @param strategyId
     * @param awardId
     */
    @Override
    public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
        StrategyAwardPO strategyAwardPO = new StrategyAwardPO();
        strategyAwardPO.setStrategyId(strategyId);
        strategyAwardPO.setAwardId(awardId);
        strategyAwardDao.updateStrategyAwardStock(strategyAwardPO);
    }

    /**
     * 根据策略ID+奖品ID的唯一值组合，查询奖品信息
     * @param strategyId 策略ID
     * @param awardId    奖品ID
     * @return 奖品信息
     */
    @Override
    public StrategyAwardEntity queryStrategyAwardEntity(Long strategyId, Integer awardId) {
        // 优先从redis中获取
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId + Constants.UNDERLINE + awardId;
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey);
        if (null != strategyAwardEntity) return strategyAwardEntity;
        // 查询数据
        StrategyAwardPO strategyAwardPO = new StrategyAwardPO();
        strategyAwardPO.setStrategyId(strategyId);
        strategyAwardPO.setAwardId(awardId);
        StrategyAwardPO strategyAwardRes = strategyAwardDao.queryStrategyAward(strategyAwardPO);
        strategyAwardEntity = StrategyAwardEntity.builder()
                .strategyId(strategyAwardRes.getStrategyId())
                .awardId(strategyAwardRes.getAwardId())
                .awardTitle(strategyAwardRes.getAwardTitle())
                .awardSubtitle(strategyAwardRes.getAwardSubtitle())
                .awardCount(strategyAwardRes.getAwardCount())
                .awardCountSurplus(strategyAwardRes.getAwardCountSurplus())
                .awardRate(strategyAwardRes.getAwardRate())
                .sort(strategyAwardRes.getSort())
                .build();
        // 缓存结果
        redisService.setValue(cacheKey, strategyAwardEntity);
        // 返回数据
        return strategyAwardEntity;
    }

    /**
     * 根据活动id, 查询activity表, 查询策略id
     * @param activityId
     * @return
     */
    @Override
    public Long queryStrategyIdByActivityId(Long activityId) {
        return activityDao.queryStrategyIdByActivityId(activityId);
    }

    /**
     * 根据treeId, 查询规则是rule_lock的节点对应的rule_value
     * @param treeIds
     * @return
     */
    @Override
    public Map<String, Integer> queryAwardRuleLockCount(String[] treeIds) {
        if (null == treeIds || treeIds.length == 0) return new HashMap<>();
        List<RuleTreeNodePO> treeNodePOS = ruleTreeNodeDao.queryAwardRuleLockCount(treeIds);
        Map<String, Integer> resultMap = new HashMap<>();
        for (RuleTreeNodePO treeNodePO : treeNodePOS) {
            String treeId = treeNodePO.getTreeId();
            Integer ruleValue = Integer.valueOf(treeNodePO.getRuleValue());
            resultMap.put(treeId, ruleValue);
        }
        return resultMap;
    }

    /**
     * 用strategyId查询activityId, 然后用userId, activityId, 查询account表, 获取到用户已经抽奖次数
     * @param userId
     * @param strategyId
     * @return
     */
    @Override
    public Integer queryActivityAccountTotalUseCount(String userId, Long strategyId) {
        Long activityId = activityDao.queryActivityIdByStrategyId(strategyId);
        RaffleActivityAccountPO accountPO = accountDao.queryActivityAccountByUserId(RaffleActivityAccountPO.builder()
                .activityId(activityId)
                .userId(userId)
                .build()
        );
        return accountPO.getTotalCount() - accountPO.getTotalCountSurplus();
    }

    /**
     * 用strategyId, 查询strategy_rule表, 获得权重配置(rule_value)
     * 再通过权重配置, 查询strategy_award表, 查询奖品相关, 组装成权重奖品配置列表ruleWeightVOS
     * 优先从redis中获取
     * @param strategyId
     * @return
     */
    @Override
    public List<RuleWeightVO> queryAwardRuleWeightByStrategyId(Long strategyId) {
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_RULE_WEIGHT_KEY + strategyId;
        List<RuleWeightVO> ruleWeightVOList = redisService.getValue(cacheKey);
        if (null != ruleWeightVOList) return ruleWeightVOList;

        ruleWeightVOList = new ArrayList<>();
        /** 1. 根据strategyId, 和规则为Rule_weight, 查询strategy_rule表, 获取对应的rule_value
         * 60:102 4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108
         */
        String ruleValue = strategyRuleDao.queryStrategyRuleValue(StrategyRulePO.builder()
                .strategyId(strategyId)
                .ruleModel(DefaultChainFactory.LogicModel.RULE_WEIGHT.getCode())
                .build()
        );

        /**
         * 2. 查询strategy_award列表, 并根据awardId, 转换成Map
         * [{101:strategyAwardPO}, {102:strategyAwardPO}, ...]
         */
        Map<Integer, StrategyAwardPO> strategyAwardPOMap = new HashMap<>();
        List<StrategyAwardPO> strategyAwardPOS = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        strategyAwardPOS.forEach(strategyAwardPO -> {
            strategyAwardPOMap.put(strategyAwardPO.getAwardId(), strategyAwardPO);
        });

        /**
         * 3. 借助 StrategyRuleEntity 实体类中的map转换方法, 转换 权重-奖品 配置
         * 需要给实体类对象传入 ruleModel, ruleValue
         * ruleModel 用于判断是否是权重规则, ruleValue做转换
         * ruleValue: 60:102 4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108
         * ruleWeightValueMap: [{"60:102":[102]}, {"4000:102,103,104,105":[102,103,104,105]}, ...]
         */
        StrategyRuleEntity strategyRuleEntity = new StrategyRuleEntity();
        strategyRuleEntity.setRuleModel(DefaultChainFactory.LogicModel.RULE_WEIGHT.getCode());
        strategyRuleEntity.setRuleValue(ruleValue);
        // [{"60:102":[102]}, {"4000:102,103,104,105":[102,103,104,105]}, ...]
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();

        // 4. 遍历权重规则Map, 组装奖品award配置
        Set<String> ruleValueSplitColons = ruleWeightValueMap.keySet();  // [60:102, 4000:102,103,104,105, ...]
        for (String ruleValueKey : ruleValueSplitColons) {  // 以 ruleValueKey = 4000:102,103,104,105 为例
            // 转换成 RuleWeightVO 对象
            List<Integer> awardIds = ruleWeightValueMap.get(ruleValueKey);  // [102,103,104,105]
            List<RuleWeightVO.Award> awardList = new ArrayList<>();
            for (Integer awardId : awardIds) {  // 102
                // 根据奖品Id, 获取对应的策略奖品实体PO
                StrategyAwardPO strategyAwardPO = strategyAwardPOMap.get(awardId);  // 102 对应的 strategyAwardPO
                awardList.add(RuleWeightVO.Award.builder()
                        .awardId(strategyAwardPO.getAwardId())
                        .awardTitle(strategyAwardPO.getAwardTitle())
                        .build());  // 循环后, awardList为 [102的AwardVO(awardId, awardTitle), 103的AwardVO(awardId, awardTitle), ....]
            }
            ruleWeightVOList.add(RuleWeightVO.builder()
                    .ruleValue(ruleValue)  // 60:102 4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108
                    .weight(Integer.valueOf(ruleValueKey.split(Constants.COLON)[0]))    // 4000
                    .awardIds(awardIds)     // [102,103,104,105]
                    .awardList(awardList)   // [102的AwardVO(awardId, awardTitle), 103的AwardVO(awardId, awardTitle), ....]
                    .build());
            // 循环后, 相当于配置有所有权重规则对应的信息
        }

        // 5. 设置缓存 - 实际场景中，这类数据，可以在活动下架的时候统一清空缓存。
        redisService.setValue(cacheKey, ruleWeightVOList);

        // 6. 返回结果
        return ruleWeightVOList;
    }
}
